/*
 * Description: 
 *     History: yang@haipo.me, 2017/03/15, create
 */

# include "me_config.h"
# include "me_balance.h"
# include "me_trade.h"

static dict_plus<balance_key*, mpd_t*> *dict_balance;

int init_balance()
{
    dict_balance = dict_plus<balance_key*, mpd_t*>::create(64);
    if (dict_balance == NULL)
        return -__LINE__;

    return 0;
}

mpd_t *balance_get(uint32_t user_id, uint32_t type, const char *asset)
{
    balance_key key;
    key.user_id = user_id;
    key.type = type;
    strncpy(key.asset, asset, ASSET_NAME_MAX_LEN );
    key.asset[ASSET_NAME_MAX_LEN] = '\0';

    auto entry = dict_balance->find(&key);
    if (entry) {
        return entry->get_value();
    }

    return NULL;
}

void balance_del(uint32_t user_id, uint32_t type, const char *asset)
{
    balance_key key;
    key.user_id = user_id;
    key.type = type;
    strncpy(key.asset, asset, ASSET_NAME_MAX_LEN);
    key.asset[ASSET_NAME_MAX_LEN] = '\0';

    dict_balance->remove(&key);
}

mpd_t *balance_set(uint32_t user_id, uint32_t type, const char *asset, mpd_t *amount)
{
    asset_type *at = get_asset_type(asset);
    if (at == NULL)
        return NULL;

    int ret = mpd_cmp(amount, mpd_zero, &mpd_ctx);
    if (ret < 0) {
        return NULL;
    } else if (ret == 0) {
        balance_del(user_id, type, asset);
        return mpd_zero;
    }

    balance_key key;
    key.user_id = user_id;
    key.type = type;
    strncpy(key.asset, asset, ASSET_NAME_MAX_LEN);
    key.asset[ASSET_NAME_MAX_LEN] = '\0';

    mpd_t *result;
    auto entry = dict_balance->find(&key);
    if (entry) {
        result = entry->get_value();
        mpd_rescale(result, amount, -at->prec_save, &mpd_ctx);
        return result;
    }

    entry = dict_balance->add(&key, amount);
    if (entry == NULL)
        return NULL;
    result =  entry->get_value();
    mpd_rescale(result, amount, -at->prec_save, &mpd_ctx);

    return result;
}

mpd_t *balance_add(uint32_t user_id, uint32_t type, const char *asset, mpd_t *amount)
{
    asset_type *at = get_asset_type(asset);
    if (at == NULL)
        return NULL;

    if (mpd_cmp(amount, mpd_zero, &mpd_ctx) < 0)
        return NULL;

    balance_key key;
    key.user_id = user_id;
    key.type = type;
    strncpy(key.asset, asset, ASSET_NAME_MAX_LEN);
    key.asset[ASSET_NAME_MAX_LEN] = '\0';

    mpd_t *result;
    auto entry = dict_balance->find(&key);
    if (entry) {
        result =  (mpd_t*)entry->val;
        mpd_add(result, result, amount, &mpd_ctx);
        mpd_rescale(result, result, -at->prec_save, &mpd_ctx);
        return result;
    }

    return balance_set(user_id, type, asset, amount);
}

mpd_t *balance_sub(uint32_t user_id, uint32_t type, const char *asset, mpd_t *amount)
{
    asset_type *at = get_asset_type(asset);
    if (at == NULL)
        return NULL;

    if (mpd_cmp(amount, mpd_zero, &mpd_ctx) < 0)
        return NULL;

    mpd_t *result = balance_get(user_id, type, asset);
    if (result == NULL)
        return NULL;
    if (mpd_cmp(result, amount, &mpd_ctx) < 0)
        return NULL;

    mpd_sub(result, result, amount, &mpd_ctx);
    if (mpd_cmp(result, mpd_zero, &mpd_ctx) == 0) {
        balance_del(user_id, type, asset);
        return mpd_zero;
    }
    mpd_rescale(result, result, -at->prec_save, &mpd_ctx);

    return result;
}

mpd_t *balance_freeze(uint32_t user_id, const char *asset, mpd_t *amount)
{
    struct asset_type *at = get_asset_type(asset);
    if (at == NULL)
        return NULL;

    if (mpd_cmp(amount, mpd_zero, &mpd_ctx) < 0)
        return NULL;
    mpd_t *available = balance_get(user_id, BALANCE_TYPE_AVAILABLE, asset);
    if (available == NULL)
        return NULL;
    if (mpd_cmp(available, amount, &mpd_ctx) < 0)
        return NULL;

    if (balance_add(user_id, BALANCE_TYPE_FREEZE, asset, amount) == 0)
        return NULL;
    mpd_sub(available, available, amount, &mpd_ctx);
    if (mpd_cmp(available, mpd_zero, &mpd_ctx) == 0) {
        balance_del(user_id, BALANCE_TYPE_AVAILABLE, asset);
        return mpd_zero;
    }
    mpd_rescale(available, available, -at->prec_save, &mpd_ctx);

    return available;
}

mpd_t *balance_unfreeze(uint32_t user_id, const char *asset, mpd_t *amount)
{
    asset_type *at = get_asset_type(asset);
    if (at == NULL)
        return NULL;

    if (mpd_cmp(amount, mpd_zero, &mpd_ctx) < 0)
        return NULL;
    mpd_t *freeze = balance_get(user_id, BALANCE_TYPE_FREEZE, asset);
    if (freeze == NULL)
        return NULL;
    if (mpd_cmp(freeze, amount, &mpd_ctx) < 0)
        return NULL;

    if (balance_add(user_id, BALANCE_TYPE_AVAILABLE, asset, amount) == 0)
        return NULL;
    mpd_sub(freeze, freeze, amount, &mpd_ctx);
    if (mpd_cmp(freeze, mpd_zero, &mpd_ctx) == 0) {
        balance_del(user_id, BALANCE_TYPE_FREEZE, asset);
        return mpd_zero;
    }
    mpd_rescale(freeze, freeze, -at->prec_save, &mpd_ctx);

    return freeze;
}

mpd_t *balance_total(uint32_t user_id, const char *asset)
{
    mpd_t *balance = mpd_new(&mpd_ctx);
    mpd_copy(balance, mpd_zero, &mpd_ctx);
    mpd_t *available = balance_get(user_id, BALANCE_TYPE_AVAILABLE, asset);
    if (available) {
        mpd_add(balance, balance, available, &mpd_ctx);
    }
    mpd_t *freeze = balance_get(user_id, BALANCE_TYPE_FREEZE, asset);
    if (freeze) {
        mpd_add(balance, balance, freeze, &mpd_ctx);
    }

    return balance;
}

int balance_status(const char *asset, mpd_t *total, size_t *available_count, mpd_t *available, size_t *freeze_count, mpd_t *freeze)
{
    *freeze_count = 0;
    *available_count = 0;
    mpd_copy(total, mpd_zero, &mpd_ctx);
    mpd_copy(freeze, mpd_zero, &mpd_ctx);
    mpd_copy(available, mpd_zero, &mpd_ctx);

    auto iter = dict_balance->get_iterator();
    for(auto entry=iter->next(); entry; entry = iter->next()) {
        balance_key *key = entry->get_key();
        if (strcmp(key->asset, asset) != 0)
            continue;
        mpd_add(total, total,  (mpd_t*)entry->val, &mpd_ctx);
        if (key->type == BALANCE_TYPE_AVAILABLE) {
            *available_count += 1;
            mpd_add(available, available,  (mpd_t*)entry->val, &mpd_ctx);
        } else {
            *freeze_count += 1;
            mpd_add(freeze, freeze,  (mpd_t*)entry->val, &mpd_ctx);
        }
    }

    iter->release();
    return 0;
}

void balance_for_each(std::function<int(balance_key*, mpd_t*)> callback) {
    if(callback == NULL) {
        return;
    }
    auto iter = dict_balance->get_iterator();
    for(auto entry = iter->next(); entry != NULL; entry=iter->next()) {
        int ret = callback(entry->get_key(), entry->get_value());
        if(ret != 0) {
            break;
        }
    }
    iter->release();
}
